package com.chat.business.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chat.business.common.thread_local.UserIdThreadLocal;
import com.chat.business.mapper.ApplyMapper;
import com.chat.business.service.IApplyService;
import com.chat.business.service.IGroupService;
import com.chat.business.service.IMessageChannelMappingService;
import com.chat.business.service.IUserService;
import com.chat.business.utils.RedisHashUtil;
import com.chat.common.common.constant.RedisKeyConstant;
import com.chat.common.dto.ProcessApplyDto;
import com.chat.common.dto.SendFriendApplyDto;
import com.chat.common.dto.SendGroupApplyDto;
import com.chat.common.entity.Apply;
import com.chat.common.entity.Group;
import com.chat.common.entity.User;
import com.chat.common.enums.ApplyState;
import com.chat.common.enums.NoticeType;
import com.chat.common.utils.DtoVoUtil;
import com.chat.common.vo.GroupVO;
import com.chat.common.vo.NoticeVO;
import com.chat.common.vo.R;
import com.chat.common.vo.UserVo;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author weikjie
 * @since 2024-03-29
 */
@Service
public class ApplyServiceImpl extends ServiceImpl<ApplyMapper, Apply> implements IApplyService {

    @Resource
    private IUserService userService;
    @Resource
    private RedisTemplate<String, Object> redisTemplateString;

    @Resource
    private IMessageChannelMappingService messageChannelMappingService;
    @Resource
    private RedisHashUtil redisHashUtil;
    @Resource
    private IGroupService groupService;

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    /**
     * 发送好友申请
     * 把申请记录存入MySQL
     * 同时向被申请人发一个“通知”
     *
     * @param sendFriendApplyDto
     * @return
     */
    @Override
    public R sendFriendApply(SendFriendApplyDto sendFriendApplyDto) {
        Apply apply = new Apply();//要存入数据库的好友申请
        //主动发出好友申请的人id
        Long activityId = UserIdThreadLocal.get();
        apply.setActiveId(activityId);
        //通过被添加的人账号查找被添加人的id
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getAccount, sendFriendApplyDto.getUserAccount());
        Long passiveId = userService.getOne(queryWrapper).getId();//查找并得到passiveId
        apply.setPassiveId(passiveId);
        //设置 验证信息，如：我是XXX
        apply.setAuthenticationInformation(sendFriendApplyDto.getAuthenticationInformation());
        //不是入群申请
        apply.setIsGroup(false);
        //设置状态为待处理
        apply.setState(ApplyState.WAITING);
        //检查一下是不是重复的申请
        LambdaQueryWrapper<Apply> applyLambdaQueryWrapper = new LambdaQueryWrapper<>();
        applyLambdaQueryWrapper.eq(Apply::getPassiveId, apply.getPassiveId())
                .eq(Apply::getActiveId, apply.getActiveId())
                .eq(Apply::getIsGroup, apply.getIsGroup());
        Apply apply1 = getOne(applyLambdaQueryWrapper);
        if (apply1 != null) {
            apply.setId(apply1.getId());
        }
        saveOrUpdate(apply);//写入数据库
        //TODO：向被申请人发一个“通知”

        //通知中的一些数据
        Map<String, Object> noticeData = new HashMap<>();
        User activityUser = userService.getById(activityId);//查询发起人信息
        UserVo activityUserVo = DtoVoUtil.entityToDtoOrVo(activityUser, UserVo.class);
        noticeData.put("activityUser", activityUserVo);//发起人信息
        noticeData.put("apply", apply);//申请记录信息
        //构建通知
        NoticeVO noticeVO = NoticeVO.builder().noticeType(NoticeType.friendApply)
                .publishId(activityId)
                .consumerId(passiveId)
                .data(noticeData)
                .build();
        //发布通知
        redisHashUtil.publishNotice(noticeVO);
        return R.succeed();
    }


    /**
     * 处理好友申请或入群申请
     * 同意 或 拒绝
     * 根据申请记录的id更新申请记录的 状态state
     *
     * @param processApplyDto
     * @return
     */
    @Override
    public R processFriendApply(ProcessApplyDto processApplyDto) {
        Apply apply = new Apply();
        apply.setId(processApplyDto.getApplyId());
        String state = processApplyDto.getState();
        //校验传来的state是否合法
        if (!state.equals(ApplyState.REJECTED) && !state.equals(ApplyState.PASSED)) {
            return R.failed("错误的ApplyState");
        }
        Apply apply1 = getById(apply.getId());
        if (apply1==null)
            return R.failed("没有id是"+apply.getId()+"的申请");
        //检验是不是本人在操作
        if (!Objects.equals(apply1.getPassiveId(), UserIdThreadLocal.get())) {
            return R.failed("你没有权限处理这条申请记录");
        }
        apply.setState(state);
        apply1.setState(state);
        updateById(apply);
        //TODO：向申请人发一个已经处理“通知”

        //通知中的一些数据
        Map<String, Object> noticeData = new HashMap<>();
        User passiveUser = userService.getById(apply1.getPassiveId());//查询被申请人信息
        UserVo passiveUserVo = DtoVoUtil.entityToDtoOrVo(passiveUser, UserVo.class);
        noticeData.put("passiveUserVo", passiveUserVo);//被申请人信息，新好友信息
        noticeData.put("apply", apply1);//申请记录信息
        //构建通知
        NoticeVO noticeVO = NoticeVO.builder().noticeType(NoticeType.friendApplyProcessed)
                .publishId(apply1.getPassiveId())
                .consumerId(apply1.getActiveId())//向发起申请的人发通知
                .data(noticeData)
                .build();
        //发布通知
        redisHashUtil.publishNotice(noticeVO);

        //如果同意，向关系表中添加一个好友关系，并返回新好友信息
        if (state.equals(ApplyState.PASSED)) {
            messageChannelMappingService.addFriendMessageChannelMapping(apply1.getActiveId(), apply1.getPassiveId());
            User activityUser = userService.getById(apply1.getActiveId());//查询申请发起人信息
            UserVo activityUserVo = DtoVoUtil.entityToDtoOrVo(activityUser, UserVo.class);
            return R.succeed(activityUserVo);
        }
        return R.succeed();
    }

    /**
     * 发送一个入群申请
     *
     * @param sendGroupApplyDto
     * @return
     */
    @Override
    @Transactional
    public R sendGroupApply(SendGroupApplyDto sendGroupApplyDto) {
        Apply apply = new Apply();
        apply.setState(ApplyState.WAITING);
        apply.setIsGroup(true);
        apply.setActiveId(UserIdThreadLocal.get());
        //用群号找群id
        LambdaQueryWrapper<Group> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Group::getGroupAccount, sendGroupApplyDto.getGroupAccount());
        Group group = groupService.getOne(queryWrapper);
        if (group==null)return R.failed("群不存在");
        apply.setPassiveId(group.getId());
        apply.setAuthenticationInformation(sendGroupApplyDto.getAuthenticationInformation());
        //检查一下是不是重复的申请
        LambdaQueryWrapper<Apply> applyLambdaQueryWrapper = new LambdaQueryWrapper<>();
        applyLambdaQueryWrapper.eq(Apply::getPassiveId, apply.getPassiveId())
                .eq(Apply::getActiveId, apply.getActiveId())
                .eq(Apply::getIsGroup, apply.getIsGroup());
        Apply apply1 = getOne(applyLambdaQueryWrapper);
        if (apply1 != null) {
            apply.setId(apply1.getId());
        }
        saveOrUpdate(apply);//将申请记录写入数据库
        //TODO：给群主发送申请通知

        //查询群主id
        Long groupLeaderId = groupService.getById(group.getId()).getGroupLeaderId();
        //构建通知体
        LambdaQueryWrapper<User> userLambdaQueryWrapper=new LambdaQueryWrapper<>();
        userLambdaQueryWrapper.eq(User::getAccount,sendGroupApplyDto.getUserAccount());
        User user = userService.getOne(userLambdaQueryWrapper);
        UserVo userVo = DtoVoUtil.entityToDtoOrVo(user, UserVo.class);
        Map<String, Object> noticeData = NoticeVO.NoticeDataBuilder.builder()
                .put("applyUserVO", userVo)//申请人信息
                .put("apply", apply)
                .put("group",group)
                .build();//申请记录信息
        NoticeVO noticeVO = NoticeVO.builder().date(new Date())
                .noticeType(NoticeType.groupApply)//入群申请
                .publishId(UserIdThreadLocal.get())
                .consumerId(groupLeaderId)
                .data(noticeData).build();
        //发送通知
        redisHashUtil.publishNotice(noticeVO);
        return R.succeed();
    }


    /**
     * 处理入群申请
     *
     * @param processApplyDto
     * @return
     */
    @Override
    @Transactional
    public R processGroupApply(ProcessApplyDto processApplyDto) {
        String state = processApplyDto.getState();
        //校验传来的state是否合法
        if (!state.equals(ApplyState.REJECTED) && !state.equals(ApplyState.PASSED)) {
            return R.failed("错误的ApplyState");
        }
        Apply apply = getById(processApplyDto.getApplyId());
        //检验是不是群主在操作
        Group group = groupService.getById(apply.getPassiveId());//查询群
        if (!Objects.equals(group.getGroupLeaderId(), UserIdThreadLocal.get())) {
            return R.failed("你没有权限处理这条申请记录");
        }
        apply.setState(state);
        updateById(apply);
        //TODO：向申请人发一个已经处理“通知”
        //构建通知体
        //查找群成员
        List<UserVo> userVoList = messageChannelMappingService.getGroupMembers(group.getId()).getData();
        GroupVO groupVO = DtoVoUtil.entityToDtoOrVo(group, GroupVO.class);
        //找群主
        User groupLeader = userService.getById(UserIdThreadLocal.get());
        //给申请人返回目前的群成员
        groupVO.setUserVoList(userVoList);
        groupVO.setGroupLeaderAccount(groupLeader.getAccount());
        Map<String, Object> map = NoticeVO.NoticeDataBuilder.builder()
                .put("groupVO", groupVO)//群信息
                .put("apply", apply)//申请记录信息
                .build();
        NoticeVO noticeVO = NoticeVO.builder().data(map)
                .date(new Date())
                .consumerId(apply.getActiveId())
                .publishId(group.getGroupLeaderId())
                .noticeType(NoticeType.groupApplyProcessed)
                .build();
        //发送通知
        redisHashUtil.publishNotice(noticeVO);
        //TODO：给所有群成员发送有新人入群通知（包括群主自己）
        if (state.equals(ApplyState.PASSED)) {
            //查询所有群成员id
            List<Long> groupMemberIds = messageChannelMappingService.getGroupMemberIds(group.getId());
            //查询申请人的信息
            User user = userService.getById(apply.getActiveId());

            //更新用户的群缓存
            //更新redis 群成员缓存
            RLock lock = redissonClient.getLock("Lock:" + RedisKeyConstant.USER_GROUP_CACHE + user.getId());
            lock.lock(5, TimeUnit.SECONDS);//加锁，阻塞式等待 5s，默认加的锁都是30s
            //先去Redis中查缓存
            String json = (String) redisTemplateString.opsForValue().get(RedisKeyConstant.USER_GROUP_CACHE + user.getId());
            List<GroupVO> groupVOS;
            if (json == null) {
                groupVOS = new ArrayList<>();
            } else {
                groupVOS = JSONUtil.toList(json, GroupVO.class);
            }
            groupVOS.add(groupVO);
            redisTemplate.opsForValue().set(RedisKeyConstant.USER_GROUP_CACHE + user.getId(), JSONUtil.toJsonStr(groupVOS));
            lock.unlock();//更新完成，释放锁

            UserVo userVo=DtoVoUtil.entityToDtoOrVo(user, UserVo.class);
            map.put("userVo",userVo);//给当前的群成员返回新人信息
            groupMemberIds.forEach(id -> {
                //构建通知体
                //发送通知
                redisHashUtil.publishNotice(NoticeVO.builder().data(map)
                        .date(new Date())
                        .consumerId(id)
                        .publishId(group.getGroupLeaderId())
                        .noticeType(NoticeType.groupNewUserIn)
                        .build());
            });
            //如果同意了入群，则在消息频道表中添加记录
            //申请人-》群
            messageChannelMappingService.addGroupMessageChannelMapping(apply.getActiveId(), group.getId());
            return R.succeed("新成员已经入群");
        }
        return R.succeed("申请记录处理完成");
    }

    /**
     * 根据请求的用户id
     * 获取所有的申请记录：有别人向他发出的，也有他发给别人的
     *
     * @return
     */
    @Override
    public R<List<Apply>> getAllApply() {
        //请求的用户id
        Long userId = UserIdThreadLocal.get();
        LambdaQueryWrapper<Apply> queryWrapper = new LambdaQueryWrapper<>();
        //查询和这个用户有关的申请记录
        queryWrapper.eq(Apply::getActiveId, userId)
                .or()
                .eq(Apply::getPassiveId, userId);
        List<Apply> list = list(queryWrapper);
        return R.succeed(list);
    }
}
